/*
 *  Sphere.cpp
 *  Cheeky Dice
 *
 *  Created by Dmitry Fadeev on 15.03.11.
 *  Copyright 2011 __MyCompanyName__. All rights reserved.
 *
 */

#include "Sphere.h"
#include <xml/Stream.h>

#ifndef M_PI
#define M_PI 3.14159265358
#endif

namespace liba
{
namespace lib3d
{
namespace node
{

const Atom Sphere::node_name("Sphere");
const Atom Sphere::shaders_name("Shaders");
const Atom Sphere::tesselation_name("Tesselation");
	
Sphere::Sphere(const Atom & name, Node * parent) : Node(name, parent), shaders(shaders_name), tesselation(8)
{
	node_type = node_name;
	hard::Manager::add_dependent(this);
}

Sphere::~Sphere()
{
	hard::Manager::remove_dependent(this);
}

Sphere * Sphere::Clone()
{
	Sphere * new_sphere = new Sphere(*this);
	hard::Manager::add_dependent(new_sphere);
	return new_sphere;
}

void Sphere::on_add_hardware(hard::Manager::hardware_type * ha)
{
	hard_vector.push_back(SweetPair(ha, new PrimitiveObject(ha->create_vertex_buffer(), ha->create_index_buffer())));
}

bool Sphere::on_node(xml::Provider * prov, const std::string & name)
{
	if(name == "Shaders")
		return prov->parse_object(&shaders);
	
	return Node::on_node(prov, name);
}
	
bool Sphere::on_attribute(xml::Provider * prov, const std::string & name, const std::string & value)
{
	if(name == "Tesselation")
		return converter::convert(value, &tesselation);

	return Node::on_attribute(prov, name, value);
}
	
void Sphere::create(int _tesselation)
{
	// save new tesselation value
	if(_tesselation < 2) 
		_tesselation = 2;
	
	if(_tesselation > 32) 
		_tesselation = 32;
	
	tesselation           = _tesselation;
	int horiz_tesselation = tesselation * 2;
	
	// compute segment steps
	float segment_step = M_PI * 2.0f / horiz_tesselation;
	
	// create arrays for vertex and index buffer data
	int vertices_count = (tesselation + 2) * horiz_tesselation;
	
	SimpleArray<float> positions(vertices_count * 3);
	SimpleArray<float> normals(vertices_count * 3);
	SimpleArray<float> uvs(vertices_count * 2);
	SimpleArray<int>   indices((tesselation - 1) * horiz_tesselation * 2 * 3);
	
	// getting arrays data
	float * positions_data = positions.data();
	float * normals_data   = normals.data();
	float * uvs_data       = uvs.data();
	int   * indices_data   = indices.data();
	
	// upper vertices
	int current_vertex = 0;
	for(int i = 0; i < horiz_tesselation; i++)
	{
		*positions_data++ = 0.0f;
		*positions_data++ = 1.0f;
		*positions_data++ = 0.0f;
		*normals_data++   = 0.0f;
		*normals_data++   = 1.0f;
		*normals_data++   = 0.0f;
		*uvs_data++       = 1.0f - ((1.0f / float(horiz_tesselation)) * (horiz_tesselation - i) - (0.5f / float(horiz_tesselation)));
		*uvs_data++       = 0.0f;
		current_vertex++;
	}
	
	// bottom vertices
	for(int i = 0; i < horiz_tesselation; i++)
	{
		*positions_data++ =  0.0f;
		*positions_data++ = -1.0f;
		*positions_data++ =  0.0f;
		*normals_data++   =  0.0f;
		*normals_data++   = -1.0f;
		*normals_data++   =  0.0f;
		*uvs_data++       =  1.0f - ((1.0f / float(horiz_tesselation)) * (horiz_tesselation - i) - (0.5f / float(horiz_tesselation)));
		*uvs_data++       =  1.0f;
		current_vertex++;
	}
	
	// sphere body
	for(int i = 1; i < tesselation; i++)
	{
		int start_vertex = current_vertex;
		for(int j = 0; j < horiz_tesselation + 1; j++)
		{
			// compute vertex angles
			float phi   = segment_step * i;
			float theta = (j == horiz_tesselation) ? 0.0f : segment_step * j;
			
			// compute coordinates
			float x     = sin(phi) * sin(theta);
			float y     = cos(phi);
			float z     = sin(phi) * cos(theta);
			
			// put values to arrays
			*positions_data++ = x;
			*positions_data++ = y;
			*positions_data++ = z;
			*normals_data++   = x;
			*normals_data++   = y;
			*normals_data++   = z;
			*uvs_data++       = 1.0f - (1.0f / float(horiz_tesselation)) * (horiz_tesselation - j);
			*uvs_data++       = (1.0f / float(tesselation)) * i;
			current_vertex++;
			
			// add indices
			// upper stip
			if(i == 1)
			{
				if(j != horiz_tesselation)
				{
					*indices_data++ = start_vertex + (j + 1);
					*indices_data++ = j;
					*indices_data++ = start_vertex + (j + 0);
				}
			}
			
			// bottom strip
			if(i == tesselation - 1)
			{
				if(j != horiz_tesselation)
				{
					*indices_data++ = j + horiz_tesselation;
					*indices_data++ = start_vertex + (j + 1);
					*indices_data++ = start_vertex + (j + 0);
				}
			}
			
			// sphere body
			if(i != 1 && tesselation != 2)
			{
				if(j != horiz_tesselation)
				{
					// first triangle
					*indices_data++ = start_vertex + (j + 1);
					*indices_data++ = start_vertex - (horiz_tesselation + 1) + (j + 1);
					*indices_data++ = start_vertex - (horiz_tesselation + 1) + (j + 0);
					
					// second triangle
					*indices_data++ = start_vertex + (j + 0);
					*indices_data++ = start_vertex + (j + 1);
					*indices_data++ = start_vertex - (horiz_tesselation + 1) + (j + 0);
				}
			}
		}
	}
	
	// send data to buffers
	SimpleArray<float> empty_float;
	HardVector::iterator it = hard_vector.begin();
	for(; it != hard_vector.end(); ++it)
	{
		// getting object
		PrimitiveObject * object = it->second;
		
		// update vertex buffer
		if(!object->vb->update(positions, normals, empty_float, uvs, uvs))
		{
			THROW(Gluck(__FUNCTION__, "vb->update() failed"));
		}
		
		// update index buffer
		if(!object->ib->update(indices.data(), indices.size()))
		{
			THROW(Gluck(__FUNCTION__, "vb->update() failed"));
		}
	}
}
	
void Sphere::RenderToList(hard::Hardware * hardware, hard::RenderList * render_list)
{
	// getting object and check if it valid
	PrimitiveObject * object = find_data(hardware);
	if(!object->vb->is_valid() || !object->ib->is_valid()) create(tesselation);
	
	// render all materials
	material::Shaders::const_iterator it = shaders.begin();
	int primitive_position = 0;
	int vertex_position    = 0;
	for(; it != shaders.end(); ++it)
	{
		// skip transparent material
		if((*it)->get_transparency() > 0.99)
			continue;
		
		// getting matrial data
		int primitive_count = (*it)->get_triangles_count();
		int vertex_count    = (*it)->get_vertex_count();
		
		// fill render model
		hard::RenderModel render_model;
		render_model.world           = GetGlobalMatrix();
		render_model.vb              = object->vb;
		//render_model.bounding_sphere = get_rep()->get_bounding_sphere();
		render_model.primitive_start = primitive_position;
		render_model.primitive_count = primitive_count;
		render_model.ib              = object->ib;
		render_model.vertex_start    = vertex_position;
		render_model.vertex_count    = vertex_count;
		
		// add to render list
		(*it)->render_to_list(hardware, &render_model.material);
		render_list->models.push_back(render_model);
		
		// update positions
		primitive_position += primitive_count * 3;
		vertex_position    += vertex_count;
	}
}

}
}
}